package task

import (
	"context"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"runtime"
	"strconv"
	"sync"
	"time"

	"sieve_engine/execute/whatsapp/lib/dto"
	"sieve_engine/execute/whatsapp/lib/protocol/app"
	"sieve_engine/execute/whatsapp/lib/protocol/utils/promise"
	"sieve_engine/execute/whatsapp/log"

	// "sieve_engine/execute/whatsapp/log"
	"sieve_engine/lib/config"
	"sieve_engine/proxy"

	"github.com/golang/protobuf/proto"
	"github.com/spf13/cast"
	"go.uber.org/zap"
)

var (
	whatsAppLoginBodyPath    = *config.String("whatsapp.login_body_path", "", "redis host")
	whatsAppAuthHandlerCount = *config.Int("whatsapp.auth_handler_count", 10, "whatsapp auth handler count")
)

type WhatsAppClient struct {
	waclient      *app.WaApp
	state         app.LoginStatus
	retryCount    int32 // retry count
	lastRetryTime time.Duration
	token         *WasTokenBean
	err           error
}

type whatsAppAuthClientsPoolServiceProvider struct {
	whatsAppTokenServiceProviderInterface
	closeChan         chan int
	lockMtx           sync.Mutex
	tokensChan        chan *WasTokenBean
	wsLoginBody       []byte
	failedClientQueue chan *WhatsAppClient
	authorizedClient  chan *WhatsAppClient
	logger            *zap.Logger
}

var (
	whatsappAuthClientOnce  sync.Once
	WhatsAppClientsProvider *whatsAppAuthClientsPoolServiceProvider
)

func NewWhatsAppAuthClientsPoolServiceProvider(close chan int) *whatsAppAuthClientsPoolServiceProvider {
	whatsappAuthClientOnce.Do(func() {
		WhatsAppClientsProvider = &whatsAppAuthClientsPoolServiceProvider{
			closeChan:         close,
			tokensChan:        make(chan *WasTokenBean, 100),
			failedClientQueue: make(chan *WhatsAppClient, 10000),
			authorizedClient:  make(chan *WhatsAppClient, 10000),
			logger:            log.CreateLogger(close, "whatsapp_clients_pool"),
		}
		err := WhatsAppClientsProvider.fillLoginBody()
		if err != nil {
			WhatsAppClientsProvider.logger.Error("fillLoginBody failed", zap.Error(err))
		}
		WhatsAppClientsProvider.subscribeToken()
		WhatsAppClientsProvider.processFailedAuth()
	})

	return WhatsAppClientsProvider
}

func (provider *whatsAppAuthClientsPoolServiceProvider) processFailedAuth() {

	go func() {
		// retry
		WhatsAppClientsProvider.retryFailed()
	}()
}

func (provider *whatsAppAuthClientsPoolServiceProvider) retryFailed() {
	for {
		select {
		case <-provider.closeChan:
			return
		case client := <-provider.failedClientQueue:
			if client.err != netError {
				// fatal error
				WhatsAppTokenProvider.tellToken(client.token, false)
				continue
			} else {
				go func() {
					client.retryCount += 1
					client.lastRetryTime = time.Second * 2
					time.AfterFunc(client.lastRetryTime, func() {
						client.token.Logger.Info("try login whatsapp again")
						err := provider.tryLogin(client)
						if err != nil {
							client.token.Logger.Info("relogin result", zap.Error(err))
							client.err = err
							provider.failedClientQueue <- client
						}
					})
				}()
			}
		}
	}
}

func (provider *whatsAppAuthClientsPoolServiceProvider) GetWhatsapp(timeout time.Duration) *WhatsAppClient {
	for {
		whatsAppClient := provider.getWaClient(timeout)
		if whatsAppClient.waclient.GetLoginStatus() == app.Drops || whatsAppClient.waclient.GetLoginStatus() == app.Disconnect ||
			whatsAppClient.waclient.GetLoginStatus() == app.NetworkError {
			whatsAppClient.token.Logger.Error("recoverable error", zap.String("phone", whatsAppClient.token.Phone), zap.String("status", whatsAppClient.waclient.GetLoginStatus().String()))
			whatsAppClient.err = netError
			provider.failedClientQueue <- whatsAppClient
		} else if whatsAppClient.waclient.GetLoginStatus() != app.Online {
			whatsAppClient.err = errors.New(fmt.Sprintf("fatal error,%s", whatsAppClient.waclient.GetLoginStatus().String()))
			whatsAppClient.token.Logger.Error("fatal error", zap.String("phone", whatsAppClient.token.Phone), zap.String("status", whatsAppClient.waclient.GetLoginStatus().String()))
			provider.failedClientQueue <- whatsAppClient
		} else {
			whatsAppClient.token.Logger.Info("getToken", zap.String("phone", whatsAppClient.token.Phone))
			return whatsAppClient
		}
	}
}

func (provider *whatsAppAuthClientsPoolServiceProvider) getWaClient(timeout time.Duration) *WhatsAppClient {

	if timeout == 0 {
		return <-provider.authorizedClient
	} else {
		ctx, cancel := context.WithTimeout(context.Background(), timeout)
		defer cancel()
		select {
		case <-ctx.Done():
			provider.logger.Error("GetWhatsapp timeout")
			return nil
		case c := <-provider.authorizedClient:
			return c
		}
	}
}

func (provider *whatsAppAuthClientsPoolServiceProvider) ReleaseWhatsapp(app *WhatsAppClient) {
	app.token.Logger.Info("releaseToken", zap.String("phone", app.token.Phone))
	provider.authorizedClient <- app
}

func (provider *whatsAppAuthClientsPoolServiceProvider) decodeMessageKeyPublicOrPrivateKey(key string) string {
	decodeKeyBytes, _ := base64.RawStdEncoding.DecodeString(key)
	size := len(decodeKeyBytes)
	if size == 66 {
		decodeKeyBytes = append(decodeKeyBytes[0:0], decodeKeyBytes[2:]...)
		decodeKeyBytes = decodeKeyBytes[:size-2] // 最后一项自己丢弃
	}
	destKeyBytes := make([]byte, hex.DecodedLen(len(decodeKeyBytes)))
	hex.Decode(destKeyBytes, decodeKeyBytes)

	// fmt.Println("keyBytes:", decodeKeyBytes)
	// fmt.Println("destKeyBytes:", destKeyBytes)
	keyStr := base64.StdEncoding.EncodeToString(destKeyBytes)
	// fmt.Println("decodeKey keyStr1:", string(destKeyBytes))
	// fmt.Println("decodeKey keyStr2:", keyStr)

	return keyStr
}

func (provider *whatsAppAuthClientsPoolServiceProvider) prepareLoginDto(serverTokenBean *WasTokenBean) (error, *dto.LoginDto) {
	loginDto := &dto.LoginDto{}
	err := json.Unmarshal(provider.wsLoginBody, loginDto)
	if err != nil {
		return err, loginDto
	}

	username := cast.ToUint64(serverTokenBean.Phone)
	loginDto.AuthBody.Username = proto.Uint64(username)
	if serverTokenBean.ClientStaticKeypair != "" && serverTokenBean.ClientStaticKeypair != "==" {
		loginDto.ClientStaticKeypair = serverTokenBean.ClientStaticKeypair
	} else {
		loginDto.StaticPriKey = serverTokenBean.StaticPriKey
		loginDto.StaticPubKey = serverTokenBean.StaticPubKey
	}

	loginDto.MsgPubKey = provider.decodeMessageKeyPublicOrPrivateKey(serverTokenBean.Ipub)
	loginDto.MsgPriKey = provider.decodeMessageKeyPublicOrPrivateKey(serverTokenBean.Ipri)
	loginDto.RegistrationId = serverTokenBean.Rid
	/*if serverTokenBean.Routing != "" {
		loginDto.EdgeRouting = serverTokenBean.Routing
	}*/

	// set proxy
	proxyIp := proxy.GetProxy("")
	if proxyIp != "" {
		loginDto.Socks5 = proxyIp
	}

	if serverTokenBean.PushName != "" {
		loginDto.AuthBody.PushName = proto.String(serverTokenBean.PushName)
	}
	if serverTokenBean.Mcc != "" {
		loginDto.AuthBody.UserAgent.Mcc = proto.String(serverTokenBean.Mcc)
	}
	if serverTokenBean.Mnc != "" {
		loginDto.AuthBody.UserAgent.Mnc = proto.String(serverTokenBean.Mnc)
	}
	if serverTokenBean.OsVersion != "" {
		loginDto.AuthBody.UserAgent.OsVersion = proto.String(serverTokenBean.OsVersion)
	}

	if serverTokenBean.Device != "" {
		loginDto.AuthBody.UserAgent.Device = proto.String(serverTokenBean.Device)
		// 成对出现
		if serverTokenBean.Manufacturer != "" {
			loginDto.AuthBody.UserAgent.Manufacturer = proto.String(serverTokenBean.Manufacturer)
		}
	}
	if serverTokenBean.OsBuildNumber != "" {
		loginDto.AuthBody.UserAgent.OsBuildNumber = proto.String(serverTokenBean.OsBuildNumber)
	}
	if serverTokenBean.PhoneId != "" {
		loginDto.AuthBody.UserAgent.PhoneId = proto.String(serverTokenBean.PhoneId)
	}

	if loginDto.AuthBody.UserAgent.LocaleLanguageIso_639_1 == nil {
		loginDto.AuthBody.UserAgent.LocaleLanguageIso_639_1 = proto.String("en")
	}
	if loginDto.AuthBody.UserAgent.LocaleCountryIso_3166_1Alpha_2 == nil {
		loginDto.AuthBody.UserAgent.LocaleCountryIso_3166_1Alpha_2 = proto.String("GB")
	}

	return nil, loginDto
}

func (provider *whatsAppAuthClientsPoolServiceProvider) fillLoginBody() error {
	bytes, err := os.ReadFile(whatsAppLoginBodyPath)
	if err != nil {
		return err
	}
	provider.wsLoginBody = bytes
	return nil
}

func (provider *whatsAppAuthClientsPoolServiceProvider) NotifyPulled(token *WasTokenBean) {
	token.Logger.Info("NotifyPulled", zap.String("phone", token.Phone))
	provider.tokensChan <- token
}

func (provider *whatsAppAuthClientsPoolServiceProvider) subscribeToken() {
	for i := 0; i < int(whatsAppAuthHandlerCount); i++ {
		go func() {
			for {
				select {
				case <-provider.closeChan:
					return
				case tbean := <-provider.tokensChan:
					err := provider.startRequestWhatsAppServerAuthorize(tbean)
					if err != nil {
						WhatsAppTokenProvider.tellToken(tbean, false)
					}
				}
			}
		}()
	}
}

func (provider *whatsAppAuthClientsPoolServiceProvider) startRequestWhatsAppServerAuthorize(tbean *WasTokenBean) error {
	provider.logger.Info("whatsapp clients pool receive token", zap.Reflect("token_bean", tbean))
	err, loginDto := provider.prepareLoginDto(tbean)
	if err != nil {
		tbean.Logger.Error("prepareLoginDtoFailed", zap.Error(err), zap.String("phone", tbean.Phone))
		return err
	}
	return provider.startLogin(loginDto, tbean)
}

func (provider *whatsAppAuthClientsPoolServiceProvider) tryLogin(whatsapp *WhatsAppClient) error {
	var result promise.Any
	var err error
	if whatsapp.retryCount >= 1 {
		whatsapp.token.Logger.Info("RetryLogin", zap.String("phone", whatsapp.token.Phone), zap.Int32("retry_count", whatsapp.retryCount))
		result, err = whatsapp.waclient.RetryLogin().GetResult()
	} else {
		whatsapp.token.Logger.Info("WALogin", zap.String("phone", whatsapp.token.Phone))
		result, err = whatsapp.waclient.WALogin().GetResult()
	}
	if err != nil {
		whatsapp.token.Logger.Error("WALogin failed", zap.Error(err), zap.String("phone", whatsapp.token.Phone))
		// disconnect
		return waneterror
	}
	ls, ok := result.(app.LoginStatus)
	if !ok || ls != app.Online {
		whatsapp.token.Logger.Error("walogin failed", zap.String("error", ls.String()), zap.String("phone", whatsapp.token.Phone))
	} else {
		whatsapp.token.Logger.Info("walogin success", zap.String("phone", whatsapp.token.Phone))
	}

	if ls != app.Online {
		if ls == app.Disconnect || ls == app.NetworkError || ls == app.Drops {
			return waneterror
		} else {
			return wafatalerror
		}
	} else {
		provider.authorizedClient <- whatsapp
		WhatsAppTokenProvider.tellToken(whatsapp.token, true)
		return nil
	}
}

var waneterror = errors.New("wa net error")
var wafatalerror = errors.New("wa fatal error")

func (provider *whatsAppAuthClientsPoolServiceProvider) startLogin(loginDto *dto.LoginDto, token *WasTokenBean) error {
	eai := app.EmptyAccountInfo(
		strconv.Itoa(int(*loginDto.AuthBody.UserAgent.AppVersion.Primary)) + "." +
			strconv.Itoa(int(*loginDto.AuthBody.UserAgent.AppVersion.Secondary)) + "." +
			strconv.Itoa(int(*loginDto.AuthBody.UserAgent.AppVersion.Tertiary)) + "." +
			strconv.Itoa(int(*loginDto.AuthBody.UserAgent.AppVersion.Quaternary)))

	if loginDto.AuthBody != nil {
		eai.SetCliPayload(loginDto.AuthBody.ClientPayload)
	} else if loginDto.AuthHexData != "" {
		// decode hex
		authData, err := hex.DecodeString(loginDto.AuthHexData)
		if err != nil {
			token.Logger.Error("DecodeString failed", zap.Error(err), zap.String("phone", token.Phone))
			return err
		}
		// set client payload Data pb
		err = eai.SetCliPayloadData(authData)
		if err != nil {
			token.Logger.Error("SetCliPayloadData failed", zap.Error(err), zap.String("phone", token.Phone))
			return err
		}
	}
	if loginDto.EdgeRouting != "" {
		routingInfo, _ := base64.StdEncoding.DecodeString(loginDto.EdgeRouting)
		eai.SetRoutingInfo(routingInfo)
	}

	var err error
	if loginDto.ClientStaticKeypair != "" {
		err = eai.SetStaticHdBase64KeyPairs(loginDto.ClientStaticKeypair)
		if err != nil {
			token.Logger.Error("SetStaticHdBase64KeyPairs failed", zap.Error(err), zap.String("phone", token.Phone))
			return err
		}
	} else {
		// set client static secret key
		err = eai.SetStaticHdBase64Keys(loginDto.StaticPriKey, loginDto.StaticPubKey)
		if err != nil {
			token.Logger.Error("SetStaticHdBase64Keys failed", zap.Error(err), zap.String("phone", token.Phone))
			return err
		}
	}
	if loginDto.MsgPriKey != "" {
		err = eai.SetMsgKey(loginDto.MsgPriKey, loginDto.MsgPubKey, loginDto.RegistrationId)
		if err != nil {
			token.Logger.Error("MsgPriKey failed", zap.Error(err), zap.String("phone", token.Phone))
			return err
		}
	}

	whatsappcli := provider.createWhatsapp(eai)

	if loginDto.Socks5 != "" {
		if runtime.GOOS != "linux" {
			whatsappcli.SetNetWorkProxy(loginDto.Socks5)
		}
	}

	whatsappClient := &WhatsAppClient{waclient: whatsappcli, retryCount: 0, token: token}

	err = provider.tryLogin(whatsappClient)
	if err != nil {
		whatsappClient.err = err
		provider.failedClientQueue <- whatsappClient
	}
	return nil
}

func (provider *whatsAppAuthClientsPoolServiceProvider) createWhatsapp(eai *app.AccountInfo) *app.WaApp {
	// new instance
	whatsappcli := app.NewWaAppCli(eai)
	// save instance
	return whatsappcli
}
